#pragma once

#include <vector>
#include <queue>
#include <unistd.h>

#include "thread.hpp"
#include "lockGuard.hpp"
#include "log.hpp"

const int g_thread_num = 3; // 表示默认创建线程个数

// 线程池本质是一个生产消费模型
template <class T>
class ThreadPool
{
public:
    pthread_mutex_t *getMutex() // 获取锁的地址
    {
        return &_lock;
    }
    bool isEmpty() // 判断队列是否为空
    {
        return task_queue_.empty();
    }

    void waitCond()
    {
        pthread_cond_wait(&_cond, &_lock); // 等待的时候释放锁，唤醒时再申请锁
    }

    T getTask()
    {
        T t = task_queue_.front();
        task_queue_.pop();
        return t;
    }

public:
    void run() // 线程池启动
    {
        for (int i = 1; i <= _num; i++)
        {
            _threads.push_back(new Thread(i, routine, this)); // 传this指针，让回调方法能够访问类
        }

        for (auto &iter : _threads)
        {
            iter->start(); // 执行thread_create函数，创建线程，创建的数量由数组大小来定，而数组大小在构造函数定义好了，
            // std::cout << iter->GetName() << "启动成功" << std::endl;
            logMessage(NORMAL, "%s%s", iter->GetName().c_str(), "启动成功");
        }
    }
    // 取任务
    // 如果定义在类内，会有隐藏this指针从而影响使用，所以加上static
    // 如果一个类内部成员用static，那么它只能使用静态成员再调用静态方法，无法使用类内的成员属性和方法
    // 如果这个静态的routine是所谓的消费线程，那么要pop队列，但是编译时会报错，这就坑了
    // 所以为了能让routine拿到类内属性，我们再上面push_back的插入Thread对象时，可以把this指针传过来，通过函数来进行访问（与其让它拿到task_queue，不如让它拿到整体对象）
    static void *routine(void *args)
    {
        ThreadData *td = static_cast<ThreadData *>(args);            // 该操作形象点说就是改文件后缀，这里的后缀是args指针
        ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(td->_args); // 然后这一步相当于解压操作，拿到指针指向对象的线程池指针
        // 消费逻辑
        // 先加锁，while(task_queue_.empty()) wait(); 如果任务队列为空就等待
        // 不为空就获取任务，然后处理，处理完就解锁
        while (true)
        {
            T task;
            {
                lockGuard lockguard(tp->getMutex()); // 通过this指针调用getMutex获得锁的地址，实现加锁，保证该代码块是安全的代码块
                while (tp->isEmpty())
                    tp->waitCond(); // 判断队列是否为空，为空就等待
                // 读取任务
                task = tp->getTask(); // 任务队列是共享的，这句话就是将任务从共享，拿到自己的私有空间
            }
            task(td->_name); // 执行任务，task是队列里的数据，也就是Task类，改类重载了operator()，所以可以直接使用圆括号执行任务

            // 测试能否传入this指针
            // tp->show();
            // sleep(1);
        }
    }
    // 往队列里塞任务
    void pushTask(const T &task)
    {
        lockGuard lockguard(&_lock); // 只单纯加锁，加了任务后还应该要唤醒对应的消费线程来消费
        task_queue_.push(task);
        pthread_cond_signal(&_cond);
    }

    static ThreadPool<T> *GetInstance()
    {
        if (nullptr == _tp) // 首次使用时创建对象，并且在加锁前先判断一次，能减少加锁解锁的次数，提高效率
        {
            pthread_mutex_lock(&_mutex);
            if (nullptr == _tp)
            {
                std::cout << "创建单例" << std::endl;
                _tp = new ThreadPool<T>();
            }
            pthread_mutex_unlock(&_mutex);
        }
        return _tp;
    }

private:
    ThreadPool(int thread_num = g_thread_num)
        : _num(thread_num)
    {
        pthread_mutex_init(&_lock, nullptr); // 初始化锁
        pthread_cond_init(&_cond, nullptr);  // 初始化条件变量
    }
    ~ThreadPool()
    {
        for (auto &iter : _threads)
        {
            iter->join(); // 在释放前join下
            delete iter;
        }
        pthread_mutex_destroy(&_lock);
        pthread_cond_destroy(&_cond);
    }
    ThreadPool(const ThreadPool<T> &) = delete;
    const ThreadPool<T> &operator=(const ThreadPool<T> &) = delete; // a=b=c

private:
    std::vector<Thread *> _threads; // 这个数组存的是将来要创建的线程
    int _num;
    std::queue<T> task_queue_; // 别人发任务来放到队列里，然后派发给指定线程去执行，所以只要添加到队列里，就自动叫醒一个线程来处理
    pthread_mutex_t _lock;
    pthread_cond_t _cond;

    // 另一种方案：
    // 我们一开始定义两个队列queue1，queue2
    // 然后再定义两个制作std::queue<T> *p_queue,  *c_queue
    // 然后p_queue->queue1,  c_queue->queue2
    // 当生产一批任务后，我们放到queue1里，然后swap(p_queue, c)queue);
    // 然后消费者处理完毕后再swap(p_queue, c_queue);
    // 所以因为我们生产和消费用的是不同的队列，未来我们进行资源任务处理的时候，仅仅只需要交换制作，而且也只要把这个交换这一句加锁即可

    static ThreadPool<T> *_tp;
    static pthread_mutex_t _mutex;
};

template <class T>
ThreadPool<T> *ThreadPool<T>::_tp = nullptr; // 静态成员一般在类外面进行初始化

template <class T>
pthread_mutex_t ThreadPool<T>::_mutex = PTHREAD_MUTEX_INITIALIZER;